#include <stdio.h>
#include <string.h>
#include "../quickjs.h"

#define JS_INIT_MODULE js_init_module
#define countof(x) (sizeof(x) / sizeof((x)[0]))

// 0  c的结构体 -->js的class
typedef struct JSTestRequest
{
    JSValue method;
    JSValue url;

    int is_alive;
} JSTestRequest;

// classId
static JSClassID js_test_req_class_id;


// 1 class的{name,finalizer,gc_mark}
// finalizer函数 析构函数
static void js_test_req_finalizer(JSRuntime *rt, JSValue val)
{
    printf("enter js_test_req_finalizer\n");
    JSTestRequest *req = JS_GetOpaque(val, js_test_req_class_id);
    if (req && req->is_alive)
    {
        printf("js_test_req_finalizer do\n");
        req->is_alive = 0;
        JS_FreeValueRT(rt, req->method);
        JS_FreeValueRT(rt, req->url);
        js_free_rt(rt, req);
    }
}

// mark gc标记回调
static void js_test_req_mark(JSRuntime *rt, JSValueConst val,
                             JS_MarkFunc *mark_func)
{
    printf("enter js_test_req_mark\n");
    JSTestRequest *req = JS_GetOpaque(val, js_test_req_class_id);
    if (req && req->is_alive)
    {
        printf("js_test_req_mark do\n");
        JS_MarkValue(rt, req->method, mark_func);
        JS_MarkValue(rt, req->url, mark_func);
    }
}

// 定义类
static JSClassDef js_test_req_class = {
    "TestReq",
    .finalizer = js_test_req_finalizer,
    .gc_mark = js_test_req_mark,
};

// 2 构造函数
static JSValue js_test_req_ctor(JSContext *ctx,
                                JSValueConst new_target,
                                int argc, JSValueConst *argv)
{
    JSTestRequest *req;

    printf("js_test_req_ctor begin\n");

    // 1. 接受构造参数 initial opaque data
    req = js_mallocz(ctx, sizeof(*req));
    if (!req)
        return JS_EXCEPTION;
    req->method = JS_DupValue(ctx, argv[0]);
    req->url = JS_DupValue(ctx, argv[1]);
    req->is_alive = 1;

    // 2. get proto
    /* using new_target to get the prototype is necessary when the class is extended. */
    JSValue proto;
    proto = JS_GetPropertyStr(ctx, new_target, "prototype");
    if (JS_IsException(proto))
        goto fail;

    // 3.  new object with proto and class_id
    JSValue obj = JS_NewObjectProtoClass(ctx, proto, js_test_req_class_id);
    JS_FreeValue(ctx, proto);

    if (JS_IsException(obj))
        goto fail;

    // 4. 关联对象与结构体 Associate with opaque data
    JS_SetOpaque(obj, req);
    printf("js_test_req_ctor end\n");
    return obj;

fail:
    js_free(ctx, req);
    JS_FreeValue(ctx, obj);
    return JS_EXCEPTION;
}

// 测试创建object函数 函数式创建
static JSValue get_req(
    JSContext *js_ctx, JSValueConst this_val,
    int argc, JSValueConst *argv)
{

    if (!JS_IsString(argv[0]) || !JS_IsString(argv[1]))
    {
        return JS_ThrowTypeError(js_ctx, "not string");
    }

    // 1. 根据calssid创建Object new an object with class_id, we already set the proto at js_inject_all()
    JSValue obj = JS_NewObjectClass(js_ctx, js_test_req_class_id);
    if (JS_IsException(obj))
    {
        printf("get_req: JS_NewObjectClass fail\n");
        return obj;
    }

    // 2. new an opaque data and save it with JS_SetOpaque
    JSTestRequest *req = js_mallocz(js_ctx, sizeof(JSTestRequest));
    if (req == NULL)
    {
        JS_FreeValue(js_ctx, obj);
        return JS_EXCEPTION;
    }

    req->is_alive = 1;
    req->method = JS_DupValue(js_ctx, argv[0]);
    req->url = JS_DupValue(js_ctx, argv[1]);

    //包含c的数据
    JS_SetOpaque(obj, req);
    return obj;
}

// 读取属性 
static JSValue js_test_req_get(JSContext *js_ctx, JSValueConst this_val,
                               int argc, JSValueConst *argv)
{
    JSTestRequest *req = JS_GetOpaque2(js_ctx, this_val, js_test_req_class_id);
    if (req == NULL)
    {
        return JS_ThrowTypeError(js_ctx, "JS_GetOpaque NULL");
    }

    if (!req->is_alive)
    {
        return JS_ThrowTypeError(js_ctx, "req not alive");
    }

    const char *key_str = JS_ToCString(js_ctx, argv[0]);
    if (key_str == NULL)
    {
        return JS_ThrowTypeError(js_ctx, "not string");
    }
    
    if (strcmp(key_str, "method") == 0)
    {
        JS_FreeCString(js_ctx, key_str);
        return JS_DupValue(js_ctx, req->method);
    }
    else if (strcmp(key_str, "url") == 0)
    {
        JS_FreeCString(js_ctx, key_str);
        return JS_DupValue(js_ctx, req->url);
    }
    else
    {
        return JS_ThrowTypeError(js_ctx, "invalid key: '%s'", key_str);
    }
}

// 释放对象
static JSValue js_test_req_close(JSContext *js_ctx, JSValueConst this_val,
                                 int argc, JSValueConst *argv)
{
    
    JSTestRequest *req = JS_GetOpaque2(js_ctx, this_val, js_test_req_class_id);
    if (req == NULL)
    {
        return JS_ThrowTypeError(js_ctx, "JS_GetOpaque NULL");
    }

    if (!req->is_alive)
    {
        return JS_ThrowTypeError(js_ctx, "req not alive");
    }

    JS_FreeValue(js_ctx, req->method);
    JS_FreeValue(js_ctx, req->url);
    req->is_alive = 0;
    js_free(js_ctx, req);

    printf("js_test_req_close\n");
    JS_SetOpaque(this_val, NULL);
    return JS_UNDEFINED;
}

// 注册get,close函数
static const JSCFunctionListEntry js_test_req_proto_funcs[] = {
    JS_CFUNC_DEF("get", 1, js_test_req_get),
    JS_CFUNC_DEF("close", 0, js_test_req_close),
};

// 2 函数注册
static int js_module_init(JSContext *js_ctx, JSModuleDef *m)
{
    JSValue global = JS_GetGlobalObject(js_ctx);

    // 1 创建classId
    JS_NewClassID(&js_test_req_class_id);
    // assert(JS_IsRegisteredClass(JS_GetRuntime(js_ctx), js_test_req_class_id) == 0);
    // 2 注册Class id, classDef {name,finalizer,gc_mark}
    JS_NewClass(JS_GetRuntime(js_ctx), js_test_req_class_id, &js_test_req_class);

    // 3 proto属性对象
    JSValue proto = JS_NewObject(js_ctx);
    // proto = {get,close}
    JS_SetPropertyFunctionList(js_ctx, proto, js_test_req_proto_funcs, countof(js_test_req_proto_funcs));
    // 注册proto到Class
    JS_SetClassProto(js_ctx, js_test_req_class_id, proto);

    // 注册全局函数get_req
    JS_SetPropertyStr(js_ctx, global, "get_req", JS_NewCFunction(js_ctx, get_req, "get_req", 0));

    // 构造函数
    JSValue req_class = JS_NewCFunction2(js_ctx, js_test_req_ctor, "TestReq", 2, JS_CFUNC_constructor, 0);
    JS_SetConstructor(js_ctx, req_class, proto); // set proto for the constructor function.

    // 注册TestReq的Class
    JS_SetPropertyStr(js_ctx, global, "TestReq", req_class);

    JS_FreeValue(js_ctx, global);
}

// 3 模块导出
#define JS_INIT_MODULE js_init_module
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
    JSModuleDef *m;
    m = JS_NewCModule(ctx, module_name, js_module_init);
    if (!m)
        return NULL;

    return m;
}